#include <cppunit/config/SourcePrefix.h>

#include "TestOptimizer.h"
#include <iostream>
#include <string>

void OptimizerTest::setUp()
{
	std::cout << "Test Optimizer setUp()\n";
}

void OptimizerTest::tearDown()
{
}

Clause OptimizerTest::createClause(int type, 
	const std::string& patternSynonym, PatternType patternType,
	bool perfectMatch1, bool perfectMatch2, bool perfectMatch3,
	RelationshipType relType, ConstraintType conType,
	ConstraintAttrType conAttrType1, ConstraintAttrType conAttrType2,
	const std::string& arg1, RelationshipArgumentType argType1,
	const std::string& arg2, RelationshipArgumentType argType2,
	const std::string& arg3, RelationshipArgumentType argType3) {

		Clause clause;
		clause.type = type;
		if (type == Clause::TYPE_RELATIONSHIP_CLAUSE) {
			// Relationships only have two args
			RelationshipEntry *relEntry = new RelationshipEntry();
			relEntry->numberOfArguments = 2;
			relEntry->relationshipType = relType;
			relEntry->relationshipArguments[0] = arg1;
			relEntry->relationshipArguments[1] = arg2;
			relEntry->relationshipArgumentTypes[0] = argType1;
			relEntry->relationshipArgumentTypes[1] = argType2;
			clause.relationshipEntry = relEntry;
		} else if (type == Clause::TYPE_PATTERN_CLAUSE) {
			// Patterns have a patternSynonym, and 2-3 args
			PatternEntry *patternEntry = new PatternEntry();
			patternEntry->synonym = patternSynonym;
			patternEntry->args.push_back(arg1);
			patternEntry->args.push_back(arg2);
			patternEntry->args.push_back(arg3);
			patternEntry->argSynonymTypes.push_back(argType1);
			patternEntry->argSynonymTypes.push_back(argType2);
			patternEntry->argSynonymTypes.push_back(argType3);
			patternEntry->perfectMatchRequirementVector.push_back(perfectMatch1);
			patternEntry->perfectMatchRequirementVector.push_back(perfectMatch2);
			patternEntry->perfectMatchRequirementVector.push_back(perfectMatch3);
			clause.patternEntry = patternEntry;
		} else {
			// Constraints (with-clauses) have 2 args
			ConstraintEntry *conEntry = new ConstraintEntry();
			conEntry->numberOfArguments = 2;
			conEntry->constraintArguments[0] = arg1;
			conEntry->constraintArguments[1] = arg2;
			conEntry->constraintArgumentTypes[0] = argType1;
			conEntry->constraintArgumentTypes[1] = argType2;
			conEntry->constraintAttrTypes[0] = conAttrType1;
			conEntry->constraintAttrTypes[1] = conAttrType2;
			conEntry->constraintType = conType;
			clause.constraintEntry = conEntry;
		}
		return clause;
}

Clause OptimizerTest::createRelationshipClause(RelationshipType relType, 
	const std::string &arg1, RelationshipArgumentType argType1, 
	const std::string &arg2, RelationshipArgumentType argType2) {
		return createClause(Clause::TYPE_RELATIONSHIP_CLAUSE,
							"", PATTERN_INVALID, false, false, false,
							relType, 
							WITH_INVALID, CONSTRAINT_ATTR_INVALID, CONSTRAINT_ATTR_INVALID,
							arg1, argType1,
							arg2, argType2,
							"", INVALID_ARG);
}

Clause  OptimizerTest::createPatternClause(PatternType pType, const std::string &synonym, RelationshipArgumentType synType, 
	const std::string &arg1, RelationshipArgumentType argType1, bool perfectMatch1,
	const std::string &arg2, RelationshipArgumentType argType2, bool perfectMatch2,
	const std::string &arg3, RelationshipArgumentType argType3, bool perfectMatch3) {
		return createClause(Clause::TYPE_PATTERN_CLAUSE,
							synonym, pType, perfectMatch1, perfectMatch2, perfectMatch3,
							INVALID_REL, 
							WITH_INVALID, CONSTRAINT_ATTR_INVALID, CONSTRAINT_ATTR_INVALID,
							arg1, argType1,
							arg2, argType2,
							arg3, argType3);
}

Clause  OptimizerTest::createConstraintClause(ConstraintType conType, 
	const std::string &arg1, RelationshipArgumentType argType1, ConstraintAttrType conAttrType1, 
	const std::string &arg2, RelationshipArgumentType argType2, ConstraintAttrType conAttrType2) {
		return createClause(Clause::TYPE_WITH_CLAUSE,
							"", PATTERN_INVALID, false, false, false,
							INVALID_REL, 
							conType, conAttrType1, conAttrType2,
							arg1, argType1,
							arg2, argType2,
							"", INVALID_ARG);
}

// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION( OptimizerTest ); // Note 4 

void OptimizerTest::testConstructor() {
	SynonymTable synTable;
	PKB pkb;
	Optimizer::ClauseSet clauses;
	Optimizer *optimizer = new Optimizer(clauses, &synTable, &pkb);
}

void OptimizerTest::testSplitIndependentClauses() {
	cout << "OptimizerTest::TestSplitIndependentClauses()\n";
	// Create an optimizer with the following clauses:
	// next*(a,b), follows(c,d), affects(b,c), next(1,10), pattern x(y,_,_),
	// with a.stmt#=b.stmt#, parent*(d,e)
	//
	// Consists of three independent sets:
	// 1) next*(a,b) follows(c,d), affects(b,c), parent*(d,e), with a.stmt#=b.stmt#
	// 2) next(1,10)
	// 3) pattern x(y,_,_)
	// 
	// and consists of one synonymless clause only:
	// 1) next(1,10)
	Optimizer::ClauseSet clauses;

	clauses.push_back( createRelationshipClause(NEXT_STAR, "a", SYNONYM_ASSIGN, "b", SYNONYM_ASSIGN) );
	clauses.push_back( createRelationshipClause(FOLLOWS, "c", SYNONYM_ASSIGN, "d", SYNONYM_PROGLINE) );
	clauses.push_back( createRelationshipClause(AFFECTS, "b", SYNONYM_ASSIGN, "c", SYNONYM_ASSIGN) );
	clauses.push_back( createRelationshipClause(NEXT, "1", INTEGER, "10", INTEGER) );
	clauses.push_back( createPatternClause(PATTERN_ASSIGN, "x", SYNONYM_ASSIGN, 
						"y", SYNONYM_VARIABLE, true,
						"_", UNDERSCORE, false,
						"_", UNDERSCORE, false) );
	clauses.push_back( createRelationshipClause(PARENT_STAR, "d", SYNONYM_PROGLINE, "e", SYNONYM_STMT) );
	clauses.push_back( createConstraintClause(WITH_INTEGER, "a", SYNONYM_ASSIGN, STMT_INDEX, "b", SYNONYM_ASSIGN, STMT_INDEX) );

	Optimizer::ClauseSets clauseSets = Optimizer::splitIndependentClauses(clauses);

	// Ensure that there are three sets
	CPPUNIT_ASSERT_EQUAL(3, (int)clauseSets.size());
}

void OptimizerTest::testExtractSynonymlessClauses() {
	cout << "OptimizerTest::TestSplitIndependentClauses()\n";
	// Create an optimizer with the following clauses:
	// next*(a,b), follows(c,d), affects(b,c), next(1,10), pattern x(y,_,_),
	// with a.stmt#=b.stmt#, parent*(d,e)
	//
	// Consists of three independent sets:
	// 1) next*(a,b) follows(c,d), affects(b,c), parent*(d,e), with a.stmt#=b.stmt#
	// 2) next(1,10)
	// 3) pattern x(y,_,_)
	// 
	// and consists of one synonymless clause only:
	// 1) next(1,10)
	Optimizer::ClauseSet clauses;

	clauses.push_back( createRelationshipClause(NEXT_STAR, "a", SYNONYM_ASSIGN, "b", SYNONYM_ASSIGN) );
	clauses.push_back( createRelationshipClause(FOLLOWS, "c", SYNONYM_ASSIGN, "d", SYNONYM_PROGLINE) );
	clauses.push_back( createRelationshipClause(AFFECTS, "b", SYNONYM_ASSIGN, "c", SYNONYM_ASSIGN) );
	clauses.push_back( createRelationshipClause(NEXT, "1", INTEGER, "10", INTEGER) );
	clauses.push_back( createPatternClause(PATTERN_ASSIGN, "x", SYNONYM_ASSIGN, 
						"y", SYNONYM_VARIABLE, true,
						"_", UNDERSCORE, false,
						"_", UNDERSCORE, false) );
	clauses.push_back( createRelationshipClause(PARENT_STAR, "d", SYNONYM_PROGLINE, "e", SYNONYM_STMT) );
	clauses.push_back( createConstraintClause(WITH_INTEGER, "a", SYNONYM_ASSIGN, STMT_INDEX, "b", SYNONYM_ASSIGN, STMT_INDEX) );

	// Before extraction, ensure that there are 7 clauses
	CPPUNIT_ASSERT_EQUAL(7, (int)clauses.size());

	// Extract!
	Optimizer::ClauseSet clauseSet = Optimizer::extractSynonymlessClauses(&clauses);

	// Ensure that there is only one synonymless clause
	CPPUNIT_ASSERT_EQUAL(1, (int)clauseSet.size());
	CPPUNIT_ASSERT(createRelationshipClause(NEXT, "1", INTEGER, "10", INTEGER) == clauseSet.at(0));
	// Ensure that the clauses set passed in for extraction has lost the synonymless clause
	// so size should now be only 6
	CPPUNIT_ASSERT_EQUAL(6, (int)clauses.size());
}

void OptimizerTest::testIsAnyClauseLeft() {
	SynonymTable synTable;
	PKB pkb;
	Optimizer::ClauseSet clauses;
	Optimizer *optimizer = new Optimizer(clauses, &synTable, &pkb);

	// Should have no clauses
	CPPUNIT_ASSERT_EQUAL(false, optimizer->isAnyClauseLeft());

	// Now test when there are clauses
	clauses.push_back(Clause());
	optimizer = new Optimizer(clauses, &synTable, &pkb);
	CPPUNIT_ASSERT_EQUAL(true, optimizer->isAnyClauseLeft());
	// Remove the least complexity clause (the only one in this case),
	// then ensure that there is no clause left
	optimizer->removeLeastComplexityClause();
	CPPUNIT_ASSERT_EQUAL(false, optimizer->isAnyClauseLeft());
}